const std = @import("std");
const Io = std.Io;
const sys = @import("syscall.zig");
const os = @import("os");

pub const Writer = struct {
    fd: sys.fd_t,
    io_w: Io.Writer,

    pub fn init(fd: sys.fd_t) Writer {
        return .{
            .fd = fd,
            .io_w = .{
                .vtable = &.{ .drain = drain, .flush = Io.Writer.noopFlush },
                .buffer = &.{},
            },
        };
    }

    fn puts(w: *Writer, bytes: []const u8) !usize {
        const nbytes = sys.write(w.fd, bytes.ptr, bytes.len);
        if (nbytes < 0) return error.WriteFailed;
        return @intCast(nbytes);
    }

    fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) !usize {
        const w: *Writer = @alignCast(@fieldParentPtr("io_w", io_w));
        return os.lib.drain(w, puts, io_w, data, splat) catch return error.WriteFailed;
    }
};

pub const Reader = struct {
    fd: sys.fd_t,
    io_r: Io.Reader,

    pub fn init(fd: sys.fd_t, buffer: []u8) Reader {
        return .{
            .fd = fd,
            .io_r = .{
                .vtable = &.{ .stream = stream },
                .buffer = buffer,
                .seek = 0,
                .end = 0,
            },
        };
    }

    /// Reads a line from the provided reader until a newline character is encountered.
    /// The returned slice does NOT include the newline character.
    /// Returns null if end-of-stream reached.
    pub fn readline(reader: *Reader) ?[]u8 {
        return reader.io_r.takeDelimiter('\n') catch |err| @panic(@errorName(err));
    }

    fn stream(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) !usize {
        const r: *Reader = @alignCast(@fieldParentPtr("io_r", io_r));
        return os.lib.stream(r, gets, io_w, limit);
    }

    fn gets(r: *Reader, buf: []u8) !usize {
        const n = sys.read(r.fd, buf.ptr, buf.len);
        if (n == 0) return error.EndOfStream else if (n < 0) return error.ReadFailed;
        return @intCast(n);
    }
};
